/* Copyright (c) 2009-2010, Sean A.O. Harney <sharney3@gmail.com> */

#include "omf.h"

/* prototypes */
EXPR_P _expr_eval(EXPR_P expr, int levels_deep);
EXPR_P _expr_eval_assignment(EXPR_P expr);
EXPR_P _expr_eval_functioncall(EXPR_P expr);
EXPR_P _new_expr(EXPR_TYPE type);
EXPR_P _new_expr_leaf(void);


/* recursively evaluate tree starting at root node expr */
/* returns EXPR_P to (for now: leaf' */

EXPR_P expr_eval(EXPR_P expr)
{
    return _expr_eval(expr, 0);
}

/* recursively evaluate tree starting at root node expr */
EXPR_P _expr_eval(EXPR_P expr, int levels_deep)
{
    EXPR_P result = NULL;
    EXPR_P res_lhs, res_rhs;
    char tabs[10];
    int i;

    for (i = 0; i < levels_deep && i < sizeof(tabs) - 1; i++)
	tabs[i] = '\t';
    tabs[i] = '\0';
    if (expr == NULL)
	error_println("expr_eval(expr = NULL.");	//assert expr not null

    debug2_printf("%sexpr_eval(type=%s, levels_deep=%d) ", tabs,
		  expr_type_to_str(expr->type), levels_deep);
    switch (expr->type)
      {
      case EXPR_LEAF:
	  {
	      debug2_printf("leaf type=%s, val: %s\n",
			    leaf_type_to_str(expr->u.leaf.type),
			    leaf_val_to_str(expr));

	      switch (expr->u.leaf.type)
		{
		case LEAF_INT:
		case LEAF_FLOAT:
		case LEAF_STRING:
		case LEAF_BOOL:
		    result = expr;
		    break;
		case LEAF_ID:
		case LEAF_DOLLARID:
		    {
			char *symname = expr->u.leaf.val.id_val;
			SYMTBL_ENT_P entry;

			entry =
			    symbol_tbl_get_entry_variable_fatal(symname);

			switch (entry->u.variable.type)
			  {
			  case VAR_TYPE_INT:
			      {
				  int val =
				      entry->u.variable.rvalue.int_val;
				  result = new_expr_leaf_int(val);
			      }
			      break;
			  case VAR_TYPE_FLOAT:
			      {
				  int val =
				      entry->u.variable.rvalue.float_val;
				  result = new_expr_leaf_float(val);
			      }
			      break;
			  case VAR_TYPE_STRING:
			      {
				  char *val =
				      entry->u.variable.rvalue.string_val;
				  result = new_expr_leaf_string(val);
			      }
			      break;
			  case VAR_TYPE_BOOL:
			      {
				  bool val =
				      entry->u.variable.rvalue.bool_val;
				  result = new_expr_leaf_bool(val);
			      }
			      break;
			  case VAR_TYPE_UNINITIALIZED:
			  default:
			      fatal_error_println("unexpected symbol table var type");	//shouldn't get here?
			  }
		    }
		}
	  }
	  break;
      case EXPR_BINARY:
	  {
	      BINARY_OP_TYPE op = expr->u.binary_op.op;
	      debug2_printf("op type=%s\n", binary_op_type_to_str(op));

	      res_lhs = _expr_eval(expr->lhs, levels_deep + 1);
	      if (res_lhs == NULL || res_lhs->type != EXPR_LEAF)
		  return NULL;
	      res_rhs = _expr_eval(expr->rhs, levels_deep + 1);
	      if (res_rhs == NULL || res_rhs->type != EXPR_LEAF)
		  return NULL;

	      LEAF_TYPE type_lhs, type_rhs;

	      type_lhs = res_lhs->u.leaf.type;
	      type_rhs = res_rhs->u.leaf.type;

	      debug2_printf("\t%sLHS type=%s, RHS type=%s\n", tabs,
			    leaf_type_to_str(type_lhs),
			    leaf_type_to_str(type_rhs));
	      if (type_lhs == type_rhs)	/* no implicit casting for typemixing needed */
		{
		    switch (type_lhs)	/* or type_rhs equally */
		      {
		      case LEAF_INT:
			  {
			      int val_lhs = res_lhs->u.leaf.val.int_val;
			      int val_rhs = res_rhs->u.leaf.val.int_val;

			      switch (op)
				{
				case BINARY_OP_ADD:
				    result =
					new_expr_leaf_int(val_lhs +
							  val_rhs);
				    break;
				case BINARY_OP_SUBTRACT:
				    result =
					new_expr_leaf_int(val_lhs -
							  val_rhs);
				    break;
				case BINARY_OP_MULTIPLY:
				    result =
					new_expr_leaf_int(val_lhs *
							  val_rhs);
				    break;
				case BINARY_OP_DIVIDE:
				    result =
					new_expr_leaf_int(val_lhs /
							  val_rhs);
				    break;
				case BINARY_OP_EQUALITY:
				    result =
					new_expr_leaf_bool(val_lhs ==
							   val_rhs);
				    break;
				default:
				    error_println
					("expr_eval(): unknown int op type: %d",
					 op);
				    return NULL;
				}
			  }
			  break;
		      case LEAF_FLOAT:
			  {
			      float val_lhs =
				  res_lhs->u.leaf.val.float_val;
			      float val_rhs =
				  res_rhs->u.leaf.val.float_val;

			      switch (op)
				{
				case BINARY_OP_ADD:
				    result =
					new_expr_leaf_float(val_lhs +
							    val_rhs);
				    break;
				case BINARY_OP_SUBTRACT:
				    result =
					new_expr_leaf_float(val_lhs -
							    val_rhs);
				    break;
				case BINARY_OP_MULTIPLY:
				    result =
					new_expr_leaf_float(val_lhs *
							    val_rhs);
				    break;
				case BINARY_OP_DIVIDE:
				    result =
					new_expr_leaf_float(val_lhs /
							    val_rhs);
				    break;
				case BINARY_OP_EQUALITY:
				    result =
					new_expr_leaf_bool(val_lhs ==
							   val_rhs);
				    break;

				default:
				    error_println
					("expr_eval(): unknown float op type: %d",
					 op);
				    return NULL;
				}
			  }
			  break;
		      case LEAF_STRING:
			  {
			      char *val_lhs =
				  res_lhs->u.leaf.val.string_val;
			      char *val_rhs =
				  res_rhs->u.leaf.val.string_val;
			      char *new_str;

			      switch (op)
				{
				case BINARY_OP_ADD:	/* concat */
				    new_str = checked_malloc(strlen(val_rhs) + strlen(val_lhs) + 1);	//TODO: need to free
				    strcat(new_str, val_lhs);	/* no need for strncat */
				    strcat(new_str, val_rhs);
				    result = new_expr_leaf_string(new_str);	// does not strdup string
				    break;
				case BINARY_OP_EQUALITY:
				    result = new_expr_leaf_bool(!strcmp(val_lhs, val_rhs));	//do strcmp, not just pointer  compare!
				    break;

				case BINARY_OP_SUBTRACT:
				case BINARY_OP_MULTIPLY:
				case BINARY_OP_DIVIDE:
				default:
				    error_println
					("expr_eval(): unknown string op type: %d",
					 op);
				    return NULL;
				}
			  }
			  break;
		      case LEAF_BOOL:
			  {
			      bool val_lhs = res_lhs->u.leaf.val.bool_val;
			      bool val_rhs = res_rhs->u.leaf.val.bool_val;

			      switch (op)
				{
				case BINARY_OP_EQUALITY:
				    result =
					new_expr_leaf_bool(val_lhs ==
							   val_rhs);
				    break;

				case BINARY_OP_ADD:
				case BINARY_OP_SUBTRACT:
				case BINARY_OP_MULTIPLY:
				case BINARY_OP_DIVIDE:
				default:
				    error_println
					("expr_eval(): unknown bool op type: %d",
					 op);
				    return NULL;
				}
			  }
			  break;

		      case LEAF_ID:
			  //TODO:
			  fatal_error_println("NO ID YET!  TODO");
			  break;
		      case LEAF_DOLLARID:
			  //TODO:
			  fatal_error_println("NO DOLLARID YET!  TODO");
			  break;

		      default:
			  error_println
			      ("expr_eval(): unknown leaf type: %d",
			       type_lhs);
			  return NULL;
		      }

	      } else		// mixed types binary op
		{
		    if (op == BINARY_OP_EQUALITY)
			result = new_expr_leaf_bool(false);	// diff types are never equal
		    else
			return NULL;	// TODO: support typemixing 
		}
	  }			//end EXPR_BINARY
	  break;
      case EXPR_ASSIGNMENT:
	  result = _expr_eval_assignment(expr);
	  break;
      case EXPR_FUNCTIONCALL:
	  result = _expr_eval_functioncall(expr);
	  break;
      default:
	  error_println("expr_eval(): unknown expression type: %d",
			expr->type);
	  return NULL;
      }

    if (result != NULL)
	debug2_printf("%sEvaluates to type: %s, val: %s\n", tabs,
		      leaf_type_to_str(result->u.leaf.type),
		      leaf_val_to_str(result));
    return result;
}


//assumes is EXPR_ASSIGNMENT
EXPR_P _expr_eval_assignment(EXPR_P expr)
{
    EXPR_P lhs = expr->lhs;
    EXPR_P rhs = expr->rhs;

    //rhs
    if (rhs == NULL)
	fatal_error_println("assignment rhs NULL");	//shouldn't ever get here

    EXPR_P rhs_result = expr_eval(rhs);
    //what will be returned: rhs_result

    if (rhs_result == NULL)
	fatal_error_println("assignment lhs NULL");	//shouldn't ever get here

    LEAF_TYPE rhs_res_type = rhs_result->u.leaf.type;

///printf("rhsresult EXPRtype=%s,leaftype=%s\n", expr_type_to_str(rhs_result->type), leaf_type_to_str(rhs_res_type));

//handle lhs
    if (lhs == NULL)
	fatal_error_println("assignment lhs NULL");	//shouldn't ever get here

    if (lhs->type != EXPR_LEAF)
	fatal_error_println("Unexpected assignment lhs  not type EXPR_LEAF");	//shouldn't ever get here

    if (lhs->u.leaf.type != LEAF_ID && lhs->u.leaf.type != LEAF_DOLLARID)
	fatal_error_println("Unexpected assignment lhs leaf is not type LEAF_ID or LEAF_DOLLARID");	//shouldn't ever get here

    char *symname;

    symname = lhs->u.leaf.val.id_val;	//id_val is a symname
    SYMTBL_ENT_P entry;
    entry = symbol_tbl_get_entry_variable_fatal(symname);

    switch (entry->u.variable.type)
      {
      case VAR_TYPE_INT:
	  if (rhs_res_type != LEAF_INT)
	      fatal_error_println
		  ("Type Mismatch assignment lhs INT rhs %s",
		   symname, leaf_type_to_str(rhs_res_type));

	  entry->u.variable.rvalue.int_val =
	      rhs_result->u.leaf.val.int_val;
	  break;
      case VAR_TYPE_FLOAT:
	  if (rhs_res_type != LEAF_FLOAT)
	      fatal_error_println
		  ("Type Mismatch assignment lhs FLOAT rhs %s",
		   symname, leaf_type_to_str(rhs_res_type));

	  entry->u.variable.rvalue.float_val =
	      rhs_result->u.leaf.val.float_val;
	  break;
      case VAR_TYPE_STRING:
	  if (rhs_res_type != LEAF_STRING)
	      fatal_error_println
		  ("Type Mismatch assignment lhs STRING rhs %s",
		   symname, leaf_type_to_str(rhs_res_type));

	  entry->u.variable.rvalue.string_val =
	      rhs_result->u.leaf.val.string_val;
	  break;
      case VAR_TYPE_BOOL:
	  if (rhs_res_type != LEAF_BOOL)
	      fatal_error_println
		  ("Type Mismatch assignment lhs BOOL rhs %s",
		   symname, leaf_type_to_str(rhs_res_type));

	  entry->u.variable.rvalue.bool_val =
	      rhs_result->u.leaf.val.bool_val;
	  break;
      case VAR_TYPE_UNINITIALIZED:
	  /* var s; s = 3; # 2nd stmt (expr stmt wth assignment expr) has lhs UNINITIALIZED) */
	  switch (rhs_res_type)
	    {
	    case LEAF_INT:
		entry->u.variable.type = VAR_TYPE_INT;	//infer type from rhs
		entry->u.variable.rvalue.int_val =
		    rhs_result->u.leaf.val.int_val;
		break;
	    case LEAF_FLOAT:
		entry->u.variable.type = VAR_TYPE_FLOAT;
		entry->u.variable.rvalue.float_val =
		    rhs_result->u.leaf.val.float_val;
		break;
	    case LEAF_STRING:
		entry->u.variable.type = VAR_TYPE_STRING;
		entry->u.variable.rvalue.string_val =
		    rhs_result->u.leaf.val.string_val;
		break;
	    case LEAF_BOOL:
		entry->u.variable.type = VAR_TYPE_BOOL;
		entry->u.variable.rvalue.bool_val =
		    rhs_result->u.leaf.val.bool_val;
		break;


	    default:		//id,dollarid,uninitialized
		fatal_error_println("Reference to variable %s : rhs type not int,float,string,bool", symname);	//shouldn't get here
	    }
	  break;
      default:
	  fatal_error_println
	      ("Reference to variable %s : unknown type.", symname);
      }
    return rhs_result;		/* value of assignment exp is rhs_result */
}

//assumes is EXPR_FUNCTIONCALL
EXPR_P _expr_eval_functioncall(EXPR_P expr)
{
    char *funcname;
    SYMTBL_ENT_P entry;
    LLIST_P asl;
    LLIST_P args_expr_list;	//llist of EXPR_P
    LLIST_P walker;
    EXPR_P result[9];
    int argc;
    char param[3];

    param[0] = '$';
    param[1] = '0';
    param[2] = '\0';

    funcname = expr->u.functioncall.funcname;
    args_expr_list = expr->u.functioncall.args_expr_list;

//all func in global symbol tbl
    entry = symbol_tbl_get_entry(glob.symbol_tbl_global, funcname);
    if (entry == NULL)
      {
	  if (!strcmp(funcname, "main"))	// empty program is OK.
	      return NULL;

	  fatal_error_println("Reference to undeclared symbol: %s",
			      funcname);
	  if (entry->type != TYPE_FUNCTION)
	      fatal_error_println("Symbol %s is not a function symbol.",
				  funcname);
      }
    /*$1 - $9; evaluated from left to right */
    for (walker = args_expr_list, argc = 1; walker != NULL && argc < 10;
	 walker = walker->next, argc++)
      {
	  EXPR_P tmp = walker->element;
	  result[argc - 1] = expr_eval(tmp);
	  if ((result[argc - 1])->type != EXPR_LEAF)	//shoulnt happen.
	      fatal_error_println("Expected EXPR_LEAF... symbol: %s",
				  funcname);
      }

//expr's were evaluated with old top of stack symtbl.
/* create new local symtbl */
//TODO: free it at func return
    HASHTBL *symbol_tbl_local = symbol_tbl_create();
    symbol_tbl_local_stack_push(symbol_tbl_local);

/* create positional params */
    /* $0 is function name */
    EXPR_P func_name_str_expr = new_expr_leaf_string(funcname);	//get leaf representation of funcname string
    symbol_tbl_new_entry_variable_from_leafexpr(symbol_tbl_local,
						param /* "$0" */ ,
						"" /* no file */ ,
						func_name_str_expr);


    for (walker = args_expr_list, argc = 1; walker != NULL && argc < 10;
	 walker = walker->next, argc++)
      {
	  (param[1])++;		//increment ASCII 0 to 1...9
//TODO: maybe remove existing?
//this func asserts it is EXPR_LEAF type expr
	  symbol_tbl_new_entry_variable_from_leafexpr(symbol_tbl_local,
						      param /* "$_" */ ,
						      "" /* no file */ ,
						      result[argc - 1]);
	  //TODO: err chk
      }




/* call the function */
    asl = entry->u.function.asl;
    debug2_printf("Processing ASL[%s]\n", funcname);
    asl_process_asl(asl);

    symbol_tbl_local = symbol_tbl_local_stack_pop();	//returns same value as symbol_tbl_local already was
    hashtbl_destroy(symbol_tbl_local);

    EXPR_P ret = glob.current_function_return;	//might be NULL
    glob.current_function_return = NULL;


    return ret;			//already eval'ed. or else is NULL if no return stmt.

}


char *expr_type_to_str(EXPR_TYPE type)
{
    switch (type)
      {
      case EXPR_LEAF:
	  return "EXPR_LEAF";
      case EXPR_BINARY:
	  return "EXPR_BINARY";
      case EXPR_ASSIGNMENT:
	  return "EXPR_ASSIGNMENT";
      default:
	  return "unknown";
      }
}

//intended for printing out
char *leaf_val_to_str(EXPR_P expr)
{
#define VALSTR_SZ 126
    char *ret = checked_malloc(VALSTR_SZ);
    //static char ret[VALSTR_SZ];
    if (expr->type != EXPR_LEAF)	// shouldn't happen but safe to check
      {
	  error_println
	      ("leaf_val_to_str() called with non-leaf expression (%s).",
	       expr_type_to_str(expr->type));
	  return "unknown: not leaf!";
      }

    switch (expr->u.leaf.type)
      {
      case LEAF_INT:
	  snprintf(ret, VALSTR_SZ, "%d", expr->u.leaf.val.int_val);
	  break;
      case LEAF_FLOAT:
	  snprintf(ret, VALSTR_SZ, "%f", expr->u.leaf.val.float_val);
	  break;
      case LEAF_STRING:
	  snprintf(ret, VALSTR_SZ, "%s", expr->u.leaf.val.string_val);
	  break;
      case LEAF_BOOL:
	  snprintf(ret, VALSTR_SZ, "%s",
		   expr->u.leaf.val.bool_val ? "true" : "false");
	  break;
      case LEAF_ID:
      case LEAF_DOLLARID:
	  {
	      char *symname;
	      SYMTBL_ENT_P entry;

	      symname = expr->u.leaf.val.id_val;
	      entry = symbol_tbl_get_entry_variable_fatal(symname);

	      switch (entry->u.variable.type)
		{
		case VAR_TYPE_INT:
		    snprintf(ret, VALSTR_SZ, "%d",
			     entry->u.variable.rvalue.int_val);
		    break;
		case VAR_TYPE_FLOAT:
		    snprintf(ret, VALSTR_SZ, "%f",
			     entry->u.variable.rvalue.float_val);
		    break;
		case VAR_TYPE_STRING:
		    snprintf(ret, VALSTR_SZ, "%s",
			     entry->u.variable.rvalue.string_val);
		    break;
		case VAR_TYPE_BOOL:
		    snprintf(ret, VALSTR_SZ, "%s",
			     entry->u.variable.rvalue.
			     bool_val ? "true" : "false");
		    break;

		case VAR_TYPE_UNINITIALIZED:
		    fatal_error_println
			("Reference to variable %s : uninitialized rvalue access.",
			 symname);
		default:
		    fatal_error_println
			("Reference to variable %s : unknown type.",
			 symname);
		}
	  }
	  break;
      default:
	  return "unknown";
      }
    return ret;
}

char *leaf_type_to_str(LEAF_TYPE type)
{
    switch (type)
      {
      case LEAF_INT:
	  return "INT";
      case LEAF_FLOAT:
	  return "FLOAT";
      case LEAF_STRING:
	  return "STRING";
      case LEAF_BOOL:
	  return "BOOL";
      case LEAF_ID:
	  return "ID";
      case LEAF_DOLLARID:
	  return "DOLLARID";
      default:
	  return "unknown";
      }
}

char *binary_op_type_to_str(BINARY_OP_TYPE type)
{
    switch (type)
      {
      case BINARY_OP_ADD:
	  return "BINARY_OP_ADD";
      case BINARY_OP_SUBTRACT:
	  return "BINARY_OP_SUBTRACT";
      case BINARY_OP_MULTIPLY:
	  return "BINARY_OP_MULTIPLY";
      case BINARY_OP_DIVIDE:
	  return "BINARY_OP_DIVIDE";
      case BINARY_OP_EQUALITY:
	  return "BINARY_OP_EQUALITY";
      default:
	  return "unknown";
      }
}

EXPR_P new_expr_leaf_int(int val)	/* for both PINT and NINT */
{
    EXPR_P expr = _new_expr_leaf();
    expr->u.leaf.type = LEAF_INT;
    expr->u.leaf.val.int_val = val;
    return expr;
}

EXPR_P new_expr_leaf_float(float val)
{
    EXPR_P expr = _new_expr_leaf();
    expr->u.leaf.type = LEAF_FLOAT;
    expr->u.leaf.val.float_val = val;
    return expr;
}

EXPR_P new_expr_leaf_string(char *val)
{
    EXPR_P expr = _new_expr_leaf();
    expr->u.leaf.type = LEAF_STRING;
    expr->u.leaf.val.string_val = val;
    return expr;
}

EXPR_P new_expr_leaf_bool(bool val)
{
    EXPR_P expr = _new_expr_leaf();
    expr->u.leaf.type = LEAF_BOOL;
    expr->u.leaf.val.bool_val = val;
    return expr;
}

EXPR_P new_expr_leaf_id(char *id)	/* id is a symname (var for now */
{
    EXPR_P expr = _new_expr_leaf();
    expr->u.leaf.type = LEAF_ID;
    expr->u.leaf.val.id_val = id;
    return expr;
}

EXPR_P new_expr_leaf_dollarid(char *id)	/* id is "$_" */
{
    EXPR_P expr = _new_expr_leaf();
    expr->u.leaf.type = LEAF_DOLLARID;
    expr->u.leaf.val.id_val = id;
    return expr;
}

EXPR_P new_expr_binary(BINARY_OP_TYPE op, EXPR_P lhs, EXPR_P rhs)
{
    EXPR_P expr = _new_expr(EXPR_BINARY);
    expr->u.binary_op.op = op;
    expr->lhs = lhs;
    expr->rhs = rhs;

    return expr;
}

EXPR_P new_expr_assignment(EXPR_P lhs, EXPR_P rhs)
{
    EXPR_P expr = _new_expr(EXPR_ASSIGNMENT);
    expr->lhs = lhs;
    expr->rhs = rhs;

    return expr;
}


//pass argslist=NULL foremptylist
EXPR_P new_expr_functioncall(char *funcname, LLIST_P args_expr_list)
{
    EXPR_P expr = _new_expr(EXPR_FUNCTIONCALL);
    expr->u.functioncall.funcname = funcname;
    expr->u.functioncall.args_expr_list = args_expr_list;
    expr->lhs = NULL;
    expr->rhs = NULL;

    return expr;

}

EXPR_P _new_expr(EXPR_TYPE type)
{
    EXPR_P expr = checked_calloc(1, sizeof(struct st_expr));	//zeroes out
    expr->type = type;
    expr->lhs = NULL;		//not really neededsince calloc
    expr->rhs = NULL;
    return expr;
}

EXPR_P _new_expr_leaf(void)
{
    EXPR_P expr = _new_expr(EXPR_LEAF);
    //already zeroed out
    return expr;
}
